package net.thermetics.fx.auth;

import com.google.appengine.api.datastore.*;

import net.thermetics.fx.auth.GaeUser.AppRole;

import org.springframework.security.core.GrantedAuthority;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.*;

public class GaeDatastoreUserRegistry implements UserRegistry {
    private static final String USER_TYPE = "GaeUser";
    private static final String USER_NICKNAME = "nickname";
    private static final String USER_EMAIL = "email";
    private static final String USER_ENABLED = "enabled";
    private static final String USER_STAFF = "staff";
    private static final String USER_AUTHORITIES = "authorities";
    private static final String USER_PREFS = "prefs";
    private static final String USER_APIKEY = "apiKey";

    @SuppressWarnings("unchecked")
	public GaeUser findUser(String userId) {
        Key key = KeyFactory.createKey(USER_TYPE, userId);
        DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

        try {
            Entity user = datastore.get(key);

            long binaryAuthorities = (Long)user.getProperty(USER_AUTHORITIES);
            Set<AppRole> roles = EnumSet.noneOf(AppRole.class);

            for (AppRole r : AppRole.values()) {
                if ((binaryAuthorities & (1 << r.getBit())) != 0) {
                    roles.add(r);
                }
            }
            
            //The example.com is a kludge that should be removed,
            //but app engine dev mode doesn't let us edit entities...
            //will fix when admin interface is added.
            if ((Boolean)user.getProperty(USER_STAFF) == true ||
            	((String)user.getProperty(USER_EMAIL)).endsWith("example.com")) {
            	roles.add(AppRole.STAFF);
            }
        	
            HashMap<String, String> prefs = null;
            try {
            	Blob prefBlob = (Blob)user.getProperty(USER_PREFS);
            	prefs = (HashMap<String, String>)deserializeObject(prefBlob.getBytes());
            }
            catch (Exception e) {
            	prefs = new HashMap<String, String>();
            	e.printStackTrace();
            }
            
            GaeUser gaeUser = new GaeUser(
                    user.getKey().getName(),
                    (String)user.getProperty(USER_NICKNAME),
                    (String)user.getProperty(USER_EMAIL),
                    roles,
                    (Boolean)user.getProperty(USER_STAFF),
                    (Boolean)user.getProperty(USER_ENABLED),
                    prefs,
                    (String)user.getProperty(USER_APIKEY));

            return gaeUser;

        } catch (EntityNotFoundException e) {
            System.out.println(userId + " not found in datastore");
            return null;
        }
    }

    public void persistUser(GaeUser gaeUser) {
        Key key = KeyFactory.createKey(USER_TYPE, gaeUser.getUserId());
        Entity userEntity = new Entity(key);
        userEntity.setProperty(USER_EMAIL, gaeUser.getEmail());
        userEntity.setProperty(USER_NICKNAME, gaeUser.getNickname());
        userEntity.setProperty(USER_STAFF, gaeUser.isStaff());
        userEntity.setProperty(USER_APIKEY, gaeUser.getApiKey());
        userEntity.setUnindexedProperty(USER_ENABLED, gaeUser.isEnabled());
        
        try {
        	byte[] serializedPrefs = serializeObject(gaeUser.getPrefs());
        	Blob prefBlob = new Blob(serializedPrefs);
        	userEntity.setUnindexedProperty(USER_PREFS, prefBlob);
        }
        catch (Exception e) {
        	e.printStackTrace();
        }

        Collection<? extends GrantedAuthority> roles = gaeUser.getAuthorities();

        long binaryAuthorities = 0;

        for (GrantedAuthority r : roles) {
            binaryAuthorities |= 1 << ((AppRole)r).getBit();
        }

        userEntity.setUnindexedProperty(USER_AUTHORITIES, binaryAuthorities);

        DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
        datastore.put(userEntity);
    }

    public void removeUser(String userId) {
        DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
        Key key = KeyFactory.createKey(USER_TYPE, userId);

        datastore.delete(key);
    }
    
    private Object deserializeObject(byte[] data) throws IOException, ClassNotFoundException {
    	ByteArrayInputStream byteStream = new ByteArrayInputStream(data);
    	ObjectInputStream deserializer = new ObjectInputStream(byteStream);
    	return deserializer.readObject();
    }
    
    private byte[] serializeObject(Serializable obj) throws IOException {
    	ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
    	ObjectOutputStream serializer = new ObjectOutputStream(byteStream);
    	serializer.writeObject(obj);
    	return byteStream.toByteArray();	
    }
}
